# waveplugs/todo.py
#
#

"""
    manage todo lists .. by user or by channel .. a time/data string can 
    be provided to set time on a todo item.
"""

## gozerlib imports

from gozerlib.utils.exception import handle_exception
from gozerlib.commands import cmnds
from gozerlib.examples import examples
from gozerlib.aliases import aliasset
from gozerlib.users import byname, users
from gozerlib.utils.timeutils import strtotime, today, striptime

## google imports

from google.appengine.ext import db

## generic import

from datetime import datetime, timedelta
from time import localtime
import time
import thread
import os

## MODEL

class GAETodo(db.Expando):

    modtime = db.DateTimeProperty(auto_now=True)
    createtime = db.DateTimeProperty(auto_now_add=True)                        
    indx = db.IntegerProperty()
    name = db.StringProperty(required=True)
    time = db.DateProperty()
    duration = db.IntegerProperty()
    warnsec = db.IntegerProperty()
    descr = db.StringProperty(required=True)
    priority = db.IntegerProperty()

## todo class

class GAETodoDb(object):

    """ database todo interface """

    def reset(self, name): 
        pass

    def size(self):

        """ return number of todo's """

        return 0
        
    def get(self, name):

        """ get todo list of <name> """

        result = db.GqlQuery("SELECT * FROM GAETodo WHERE name = :1 ORDER BY priority DESC", name).fetch(1000)
        return result

    def getid(self, name, idnr):

        """ get todo data of <idnr> """

        result = db.GqlQuery("SELECT * FROM GAETodo WHERE name = :1", name).fetch(1000)

        for res in result:
             if res.key().id() == idnr:
                 return res


    def setprio(self, who, todonr, prio):

        """ set priority of todonr """

        todo = self.getid(who, todonr)

        if todo:
            todo.priority = prio
            todo.put()
            return 1

    def getprio(self, name, todonr):

        """ get priority of todonr """

        todo = self.getid(name, todonr)

        if todo:
            return todo.priority

    def getwarnsec(self, todonr):
        """ get priority of todonr """
        return 0

    def settime(self, who, todonr, ttime):

        """ set time of todonr """

        todo = self.getid(who, todonr)

        if todo:
            todo.time = datetime.fromtimestamp(ttime)
            todo.put()
            return 1

    def add(self, name, txt, ttime=0, duration=0, warnsec=0, priority=0, alarmnr=None):

        """ add a todo """

        name = name.lower()
        txt = txt.strip()
        todo = GAETodo(name=name, descr=txt)
        todo.name = name
        todo.descr = txt
        todo.ttime = ttime
        todo.duration = duration
        todo.warnsec = warnsec
        todo.priority = priority
        todo.alarmnr = alarmnr
        todo.put()
        return todo.key().id()

    def delete(self, name, indexnr):

        """ delete todo item """

        name = name.lower()
        items = db.GqlQuery("SELECT * FROM GAETodo WHERE name = :1", name).fetch(1000)
        got = 0

        for item in items:
             if item.key().id() == indexnr:
                 item.delete()
                 got += 1

        return got

    def toolate(self, name):

        """ show if there are any time related todoos that are too late """

        items = db.GqlQuery("SELECT * FROM GAETodo WHERE name = :1 AND time < :2 ORDER BY priority DESC", name, datetime.now()).all()

        return items

    def withintime(self, name, time1, time2):

        """ show todo list within time frame """

        name = name.lower()
        items = db.GqlQuery("SELECT * FROM GAETodo WHERE name = :1 AND time > :2 AND time < :3 ORDER BY priority DESC", name, time1, time2).get()

        return items

    def timetodo(self, name):

        """ show all time related todoos. """

        name = name.lower()
        items = db.GqlQuery("SELECT * FROM GAETodo WHERE name = :1 AND time > :2 ORDER BY priority DESC", name, datetime.min).get()

        return items

todo = GAETodoDb()

assert(todo)

def size():

    """ return number of todo entries """

    return todo.size()


def handle_todo(bot, ievent):

    """ 
        todo [<item>] .. show todo's or set todo item .. a time/date can be \
        given.

    """
    if len(ievent.args) > 0:
        handle_todo2(bot, ievent)
        return

    name = users.getname(ievent.auth)

    if not name:
        ievent.reply("can't find username for %s" % ievent.userhost)
        return

    try:
        todoos = todo.get(name)
    except KeyError:
        ievent.reply('i dont have todo info for %s' % user.name)
        return

    saytodo(bot, ievent, todoos)

def handle_todo2(bot, ievent):

    """ set todo item """

    if not ievent.rest:
        ievent.missing("<what>")
        return
    else:
        what = ievent.rest

    name = users.getname(ievent.auth)

    if not name:
        ievent.reply("can't find username for %s" % ievent.userhost)
        return

    ttime = strtotime(what)
    nr = 0

    if not ttime == None:
        ievent.reply('time detected ' + time.ctime(ttime))
        nr = todo.add(name, what, ttime)
    else:
        nr = todo.add(name, what)

    ievent.reply('todo %s added' % nr)

cmnds.add('todo', handle_todo, 'USER')
examples.add('todo', 'todo [<item>] .. show todo items or add a todo item', \
'1) todo 2) todo program the bot 3) todo 22:00 sleep')
#tests.add('todo program the bot').add('todo', 'program the bot')

def handle_tododone(bot, ievent):

    """ todo-done <listofnrs> .. remove todo items """

    if len(ievent.args) == 0:
        ievent.missing('<list of nrs>')
        return

    try:
        nrs = []
        for i in ievent.args:
            nrs.append(int(i))
    except ValueError:
        ievent.reply('%s is not an integer' % i)
        return

    name = users.getname(ievent.auth)

    if not name:
        ievent.reply("can't find username for %s" % ievent.userhost)
        return

    nrdone = 0
    failed = []

    for i in nrs:
        try:
            todo.delete(name, i)
            nrdone += 1
        except Exception, ex:
            failed.append(str(i))
            handle_exception()

    if failed:
        ievent.reply('failed to delete %s' % ' .. '.join(failed))

    if nrdone == 1:
        ievent.reply('%s item deleted' % nrdone)
    elif nrdone == 0:
        ievent.reply('no items deleted')
    else:
        ievent.reply('%s items deleted' % nrdone)

cmnds.add('todo-done', handle_tododone, 'USER')
examples.add('todo-done', 'todo-done <listofnrs> .. remove items from \
todo list', '1) todo-done 1 2) todo-done 3 5 8')
aliasset('done', 'todo-done')
#tests.add('todo program the bot', 'todo item (\d+) added').add('todo-done %s', '(\d+) item deleted')

def handle_chantodo(bot, ievent):

    """
        todo-chan [<item>] .. show channel todo's or set todo item for \
        channel.

    """
    if ievent.rest:
        handle_chantodo2(bot, ievent)
        return

    todoos = todo.get(ievent.channel)
    saytodo(bot, ievent, todoos)

def handle_chantodo2(bot, ievent):

    """ set todo item for channel. """

    what = ievent.rest
    ttime = strtotime(what)
    nr = 0

    if not ttime  == None:
        ievent.reply('time detected ' + time.ctime(ttime))
        what = striptime(what)
        nr = todo.add(ievent.channel, what, ttime)
        result = '#%s (%s) %s' % (nr, ievent.nick, what)
        alarmnr = alarms.add(bot.name, ievent.channel, ttime, result)
    else:
        result = '(%s) ' % ievent.nick + what
        nr = todo.add(ievent.channel, result, 0)

    ievent.reply('todo item %s added' % nr)

cmnds.add('todo-chan', handle_chantodo, 'USER')
examples.add('todo-chan', 'todo-chan [<item>] .. add channel todo', \
'todo-chan fix bla')
aliasset('chantodo', 'todo-chan')
#tests.add('todo-chan program the bot', 'todo item (\d+) added').add('todo-chan', 'program the bot')

def handle_todochandone(bot, ievent):

    """ todo-chandone <listofnrs> .. remove channel todo item. """

    if not ievent.rest:
        ievent.missing('<list of nrs>')
        return

    data = ievent.rest.split()

    try:
        nrs = []
        for i in data:
            nrs.append(int(i))
    except ValueError:
        ievent.reply('%s is not an integer' % i)
        return

    nrdone = 0
    failed = []

    for i in nrs:
        try:
            if todo.delete(ievent.channel, i):
                nrdone += 1
        except Exception, ex:
            failed.append(str(i))
            handle_exception()

    if failed:
        ievent.reply('failed to delete: ', failed)

    if nrdone == 1:
        ievent.reply('%s item deleted' % nrdone)
    elif nrdone == 0:
        ievent.reply('no items deleted')
    else:
        ievent.reply('%s items deleted' % nrdone)

cmnds.add('todo-chandone', handle_todochandone, 'USER')
examples.add('todo-chandone', 'todo-chandone <listofnrs> .. remove item \
from channel todo list', 'todo-chandone 2')
aliasset('chandone', 'todo-chandone')
#tests.add('todo-chan program the bot', 'todo item (\d+) added').add('todo-chandone %s', '(\d+) item deleted')

def handle_settodo(bot, ievent):

    """ todo-set <name> <txt> .. add a todo to another user's todo list. """

    try:
        who = ievent.args[0]
        what = ' '.join(ievent.args[1:])
    except IndexError:
        ievent.missing('<nick> <what>')
        return

    if not what:
        ievent.missing('<nick> <what>')
        return

    userhost = who

    if not userhost:
        ievent.reply("can't find userhost for %s" % who)
        return

    whouser = userhost

    if not whouser:
        ievent.reply("can't find user for %s" % userhost)
        return

    name = users.getname(ievent.auth)

    if not name:
        ievent.reply("can't find username for %s" % ievent.userhost)
        return

    what = "%s: %s" % (ievent.nick, what)
    ttime = strtotime(what)
    nr = 0

    if not ttime  == None:
        ievent.reply('time detected ' + time.ctime(ttime))
        what = striptime(what)
        nr = todo.add(whouser, what, ttime)
        what = "#%s %s" % (nr, what)
        alarmnr = alarms.add(bot.name, who, ttime, what)
    else:
        nr = todo.add(whouser, what, 0)

    ievent.reply('todo item %s added' % nr)

cmnds.add('todo-set', handle_settodo, 'USER')
examples.add('todo-set', 'todo-set <nick> <txt> .. set todo item of \
<nick>', 'todo-set dunker bot proggen')
#tests.add('user-addpermit owner todo').add('todo-set {{ me }} program the bot', 'todo item (\d+) added')

def handle_gettodo(bot, ievent):

    """ todo-get <nick> .. get todo of another user. """

    try:
        who = ievent.args[0]
    except IndexError:
        ievent.missing('<nick>')
        return

    whouser = userhost = who
    name = users.getname(ievent.auth)
    todoos = todo.get(whouser)
    saytodo(bot, ievent, todoos)

cmnds.add('todo-get', handle_gettodo, ['USER', 'WEB'])
examples.add('todo-get', 'todo-get <nick> .. get the todo list of \
<nick>', 'todo-get dunker')
#tests.add('todo program the bot').add('todo-get {{ me }}', 'program the bot')

def handle_todotime(bot, ievent):

    """ todo-time .. show time related todoos. """

    name = users.getname(ievent.auth)

    if not name:
        ievent.reply("can't find username for %s" % ievent.userhost)
        return

    todoos = todo.timetodo(name)
    saytodo(bot, ievent, todoos)

cmnds.add('todo-time', handle_todotime, 'USER')
examples.add('todo-time', 'todo-time .. show todo items with time fields', \
'todo-time')
aliasset('tt', 'todo-time')
#tests.add('todo 11-11-2011 test', 'time detected').add('todo-time', 'test')

def handle_todoweek(bot, ievent):

    """ todo-week .. show time related todo items for this week. """

    name = users.getname(ievent.auth)

    if not name:
        ievent.reply("can't find username for %s" % ievent.userhost)
        return

    todoos = todo.withintime(name, today(), today()+7*24*60*60)
    saytodo(bot, ievent, todoos)

cmnds.add('todo-week', handle_todoweek, 'USER')
examples.add('todo-week', 'todo-week .. todo items for this week', 'todo-week')
#tests.add('tomorrow test the bot').add('todo-week', 'test the bot')

def handle_today(bot, ievent):

    """ todo-today .. show time related todo items for today. """

    name = users.getname(ievent.auth)

    if not name:
        ievent.reply("can't find username for %s" % ievent.userhost)
        return

    todoos = todo.withintime(name, today(), today()+24*60*60)
    saytodo(bot, ievent, todoos)

cmnds.add('todo-today', handle_today, 'USER')
examples.add('todo-today', 'todo-today .. todo items for today', 'todo-today')
aliasset('today', 'todo-today')
#tests.add('todo test the bot').add('todo-today', 'test the bot')

def handle_tomorrow(bot, ievent):

    """ todo-tomorrow .. show time related todo items for tomorrow. """

    username = users.getname(ievent.auth)

    if not username:
        ievent.reply("can't find username for %s" % ievent.userhost)
        return

    if ievent.rest:
        what = ievent.rest
        ttime = strtotime(what)

        if ttime != None:

            if ttime < today() or ttime > today() + 24*60*60:
                ievent.reply("%s is not tomorrow" % \
time.ctime(ttime + 24*60*60))
                return

            ttime += 24*60*60
            ievent.reply('time detected ' + time.ctime(ttime))
            what = striptime(what)

        else:
            ttime = today() + 42*60*60

        todo.add(username, what, ttime)   
        ievent.reply('todo added')    
        return

    todoos = todo.withintime(username, today()+24*60*60, today()+2*24*60*60)
    saytodo(bot, ievent, todoos)

cmnds.add('todo-tomorrow', handle_tomorrow, 'USER')
examples.add('todo-tomorrow', 'todo-tomorrow .. todo items for tomorrow', \
'todo-tomorrow')
aliasset('tomorrow', 'todo-tomorrow')
#tests.add('todo-tomorrow test the bot').add('todo-time', 'test the bot')

def handle_setpriority(bot, ievent):

    """
        todo-setprio [<channel|name>] <itemnr> <prio> .. show priority \
        on todo item.

     """

    try:
        (who, itemnr, prio) = ievent.args
    except ValueError:

        try:
            (itemnr, prio) = ievent.args
            who = users.getname(ievent.auth)
        except ValueError:
            ievent.missing('[<channe|namel>] <itemnr> <priority>')
            return

    if not who:
        ievent.reply("can't find username for %s" % ievent.userhost)
        return

    try:
        itemnr = int(itemnr)
        prio = int(prio)
    except ValueError:
        ievent.missing('[<channel|name>] <itemnr> <priority>')
        return

    who = who.lower()
    todo.setprio(who, itemnr, prio)
    ievent.reply('priority set')

cmnds.add('todo-setprio', handle_setpriority, 'USER')
examples.add('todo-setprio', 'todo-setprio [<channel|name>] <itemnr> <prio> \
.. set todo priority', '1) todo-setprio #dunkbots 2 5 2) todo-setprio owner \
3 10 3) todo-setprio 2 10')
aliasset('setprio', 'todo-setprio')
#tests.add('todo test the bot', 'todo item (\d+) added').add('todo-setprio %s 10', 'priority set')

def handle_todosettime(bot, ievent):

    """ 
        todo-settime [<channel|name>] <itemnr> <timestring> .. set time \
        on todo item.

     """

    ttime = strtotime(ievent.rest)

    if ttime == None:
        ievent.reply("can't detect time")
        return   

    txt = striptime(ievent.rest)

    try:
        (who, itemnr) = txt.split()
    except ValueError:

        try:
            (itemnr, ) = txt.split()
            who = users.getname(ievent.auth)
        except ValueError:
            ievent.missing('[<channel|name>] <itemnr> <timestring>')
            return

    if not who:
        ievent.reply("can't find username for %s" % ievent.userhost)
        return

    try:
        itemnr = int(itemnr)
    except ValueError:
        ievent.missing('[<channel|name>] <itemnr> <timestring>')
        return

    who = who.lower()

    if not todo.settime(who, itemnr, ttime):
        ievent.reply('no todo %s found for %s' % (itemnr, who))
        return

    ievent.reply('time of todo %s set to %s' % (itemnr, time.ctime(ttime)))

cmnds.add('todo-settime', handle_todosettime, 'USER')
examples.add('todo-settime', 'todo-settime [<channel|name>] <itemnr> \
<timestring> .. set todo time', '1) todo-settime #dunkbots 2 13:00 2) \
todo-settime owner 3 2-2-2010 3) todo-settime 2 22:00')
#tests.add('todo test the bot', 'todo item (\d+) added').add('todo-settime %s 11:00', 'time of todo %s set')

def handle_getpriority(bot, ievent):

    """
        todo-getprio <[channel|name]> <itemnr> .. get priority of todo \
        item.

    """

    try:
        (who, itemnr) = ievent.args
    except ValueError:

        try:
            itemnr = ievent.args[0]
            who = users.getname(ievent.auth)
        except IndexError:
            ievent.missing('[<channel|name>] <itemnr>')
            return

    if not who:
        ievent.reply("can't find username for %s" % ievent.userhost)
        return

    try:
        itemnr = int(itemnr)
    except ValueError:
        ievent.missing('[<channel|name>] <itemnr>')
        return

    who = who.lower()
    prio = todo.getprio(who, itemnr)
    ievent.reply('priority is %s' % prio)

cmnds.add('todo-getprio', handle_getpriority, 'USER')
examples.add('todo-getprio', 'todo-getprio [<channel|name>] <itemnr> .. get \
todo priority', '1) todo-getprio #dunkbots 5 2) todo-getprio 3')
aliasset('prio', 'todo-getprio')
#tests.add('todo test the bot', 'todo item (\d+) added').add('todo-getprio %s', 'priority is 0')

def saytodo(bot, ievent, todoos):

    """ output todo items of <name>. """

    result = []
    now = datetime.now()

    if not todoos:
        ievent.reply('nothing todo ;]')
        return

    counter = 0

    for i in todoos:
        counter += 1
        res = ""
        res += "%s - " % i.key().id()

        if i.priority:
            res += "[%s] " % i.priority

        if i.time and not i.time == i.time.min:
            if i.time < now:
                res += 'TOO LATE: '
            res += "%s %s " % (i.time.ctime(), i.descr)
        else:
            res += "%s " % i.descr

        result.append(res.strip())

    if result:
        ievent.reply("todo: ", result)
